序列化探索 - Fastjson
开局一吐槽,Fastjson的文档,比Jackson还差。Jackson只是位置不明确,如果安下心来看看,还是能够理清楚的。而Fastjson是位置不明确,如果安下心来看看,还会发现,它的文档零零散散,中英文混杂,找不准主线在哪儿。我记得知乎上有个问题,fastjson这么快老外为啥还是热衷 jackson?,就这文档,让老外用个啥。
不过看还是要看的,毕竟它是目前主流序列化框架之一。老样子,我们还是从基本使用方法和原理分析两部分着手。
能力
Fastjson仅仅针对json,尚不支持其它任何格式,也没有看到谁为它进行格式扩展。因为这并不是它的目的,库如其名,它是为了更快地序列化Json而存在。因此,使用上来,会简单许多。具体分为几个部分
- 常规使用
- 注解
- 自定义序列化器
- 自定义过滤器
- 树模型
基础能力
1 | fun testBase() { |
序列化:
JSON.toJSONString(对象)
反序列化:
JSON.parseObject(jsonString, 对象class)
注意
默认情况下,是根据getter和setter方法取得和设置字段的,如果没有,将得不到输出
也可以基于属性获取和设置字段,考虑下面的例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16fun testBasedOnFields() {
data class Resource(
private var id: String,
private var type: Type
)
// 设置基于字段的序列化
val serializeConfig = SerializeConfig(true)
// 设置基于字段反序列化
val parserConfig = ParserConfig(true)
val jsonString = JSON.toJSONString(Resource("1", Type.RECORD), serializeConfig)
println(jsonString)
val resource = JSON.parseObject<Resource>(jsonString, Resource::class.java, parserConfig)
println(resource)
}
注解
注解方面它采取了另一种思路,它只有两个注解,但将功能放在注解的属性中
- @JSONType:放在类上的注解 ,可设置
- alphabetic:字段按照字母顺序排序
- asm:反序列化时是否使用asm
- orders:字段之间的顺序
- includes:需要包含哪些字段
- excludes:需要排除哪些字段
- serializeFeatures:需要包含的序列化功能
- parseFeatures:需要包含的反序列化功能
- mappingTo:映射成某个类
- builder:指定反序列化的构建器
- serializer:指定序列化器
- deserializer:指定反序列化器
- naming:指定命名策略
- serialzeFilters:指定过滤器
- @JSONField:放在属性上的注解 ,可设置
- ordinal:序列化后字段的位置
- name:序列化后的名字
- format:指定日期的格式
- serialize:是否参与序列化
- deserialize:是否参与反序列化
- serializeFeatures:需要包含的序列化功能
- parseFeatures:需要包含的反序列化功能
- label:标签,这是内部功能,结合过滤器可实现类似分组的功能
- serializeUsing:指定序列化器
- deseializeUsing:指定反序列化器
- alternateNames:指定别名,反序列化时可用
- unwrapped:将带有结构的对象的属性提取到顶层
- defaultValue:反序列化时的默认值
1 | // 一个简单的修改属性名字献给大家 |
仔细想想,从注解能力上来说,还是有所差别的
- @JsonRawValue,将字段作为原生json看待
- @JsonAutoDetect,自定义属性检测的可见性修饰符
- @JsonView,同一个POJO的多种序列化结果,可通过label实现
- @JsonAnyGetter、@JsonAnySetter,多余的字段塞到map,以及反过程
- @JsonValue,将POJO的某个字段作为整个POJO的序列化结果
- @JsonInclude,根据情况决定是否将字段加入序列化
- @JsonEnumDefaultValue,给枚举设置默认值
- @JsonInject,给某个字段强行注入
- 多态,不过可以通过其它方式变相达成:SerializeFeature.WriteClassName
- @JsonManagedReference等循环引用的解决方式,Fastjson也有解决,不过它是采用$ref的方式,而非jackson的去除或使用某个字段替代。
- @JsonRootName,将对象序列化到一个指定名称的属性下
自定义序列化器
不多解释,传统技能:为LocalDateTime自定义序列化器
1 | fun testCustomSerializer() { |
不过这序列化器注册的方式嘛,是不是不大友好呀。要么全局注册,要么序列化时传参进去,并不能持有多套配置JSON对象。
自定义过滤器
过滤器是Fastjson比较独有的概念,也比较好理解:在序列化的多个阶段提供给用户参与调整的机会。
具体怎么用,只能靠猜,源码也没有注释,文档也不好找,需要的时候再去找吧。这里就展示怎么注册:把所有名称都设置为大写
1 | fun testCustomFilter() { |
树模型的使用
没错,它也有树模型,只是功能没那么强大而已,它也只有两个对象
- JSONObject:可直接理解为Map,事实上它也是继承了Map
- JSONArray:理解为List
1 | fun testTreeModel() { |
输出如下,这里可以看到一个问题:Fastjson默认序列化后所有的字段都是排序的,这就很不好。
1 | { |
其它功能
其它功能,主要就是在Feature中指定的内容了,基本都是置顶序列化和反序列化时遵循的特性。
1 | // 序列化相关的功能 |
原理
看了kotlinx.serialization、Jackson,再看Fastjson,发现它们的组成基本一致,无非三个部分,可能根据情况其命名和具体实现方式会有所不同。这部分没啥新意,自己追一下方法就OK了。
- 门面:JSON
- 将原始对象写入流:SerializeWriter、JSONReaderScanner
- 将自定义对象转换为原始对象:ObjectSerializer、ObjectDeserializer
Fastjson的特点在于快,为什么这么快呢?据说是算法,作者自己的博客——Fastjson技术内幕有所描述,归结起来大概就是
- 自定义SerializeWriter,提供两部并做一步走之类的方法writeIntAndChar,减少越界检查
- 使用ASM避免反射
- 自定义IdentityHashMap,避免equals操作
- 默认字段有序,以便为反序列化性能提升做准备
- balabalabalabalabala
所以Fastjson的原理,重点是算法,而不在结构上。而这些算法,是不是有点奇技淫巧了🤔。
总结
看了官方手册、网上相关文章,翻阅了源码,试用了基本功能,Fastjson给人最大的感觉——偏科生。
偏在哪里?速度,大家都在强调快快快,一切以快为目标。作者告诉我们Fastjson是目前已知的最快的Json序列化库,给出benchmark,晒出获奖记录,好像要脚踩Jackson,拳打Gson,唯我独尊,甚至和Protobuf进行了对比(这个对比我觉得就很扯);网文介绍Fastjson的功能时,快是一定要强调的。大家好像陷入一种狂热状态,好像Json序列化最主要的功能就是为了快,这明显是不正常的。
典型的Web场景,数据库读取几十ms,序列化几ms,如果序列化不是以指数形式加速,快个小几倍个人认为意义不大,可能对于京东淘宝这种超大吞吐量有意义,对一般的网站,Jackson已经足够快速。
作为一个序列化库,更重要的是稳定性、安全性、可用性。这几方面FastJson做的貌似都不大好,文档混乱、代码没有注释、类命名还是有奇怪的地方,当然作为一个人撸出来的代码,也不能苛求太多。
对此,我个人的观点是,Fastjson只适合少数特定场景下的使用,并不能作为一个通用的Json序列化框架。目前看起来,我用Jackson。